#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_bt.h"
#include "driver/uart.h"
#include "string.h"

#include "esp_gap_ble_api.h"
#include "esp_gatts_api.h"
#include "esp_bt_defs.h"
#include "esp_bt_main.h"
#include "ble_spp_server_demo.h"

//add
#include "ble_data_handle.h"
#include "esp_partition.h"
#include "flash.h"
#include "driver/gpio.h"
#include "driver/uart.h"

//add
#include "gpio.h"
#include "esp32/rom/md5_hash.h"
#include "mbedtls/md5.h"

#define TAG "BURM"

static const char *BURN_TAG = "BURN";
static const char *RX_TASK_TAG = "<<<<";
static const char *TX_TASK_TAG = ">>>>";

static const int RX_BUF_SIZE = 1024;
static const int TX_BUF_SIZE = 1024;
extern xQueueHandle gpio_evt_queue;
extern xQueueHandle led_queue;

#define TXD_PIN (GPIO_NUM_17)
#define RXD_PIN (GPIO_NUM_16)

uart_config_t uart_config = {
    .baud_rate = 115200,
    .data_bits = UART_DATA_8_BITS,
    .parity = UART_PARITY_DISABLE,
    .stop_bits = UART_STOP_BITS_1,
    .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
    .source_clk = UART_SCLK_APB,
};

void init(void)
{
    // We won't use a buffer for sending data.
    uart_driver_install(UART_NUM_1, RX_BUF_SIZE * 2, 0, 0, NULL, 0);
    uart_param_config(UART_NUM_1, &uart_config);
    uart_set_pin(UART_NUM_1, TXD_PIN, RXD_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
}

int sendData(const char *logName, const char *data, int len)
{
    const int txBytes = uart_write_bytes(UART_NUM_1, data, len);
    ESP_LOGI(logName, "Wrote %d bytes", txBytes);
    return txBytes;
}

/*
    操作码
*/
#define UP_OPCODE_GET_TYPE 0x00
#define UP_OPCODE_SEND_TYPE 0x01
#define UP_OPCODE_CHANGE_BANDRATE 0x12
#define UP_OPCODE_CHANGE_BANDRATE_ACK 0x13
#define UP_OPCODE_BLOCK_ERASE 0x0C
#define UP_OPCODE_BLOCK_ERASE_ACK 0x0D
#define UP_OPCODE_WRITE_RAM 0x04
#define UP_OPCODE_WRITE_RAMACK 0x05
#define UP_OPCODE_EXECUTE_CODE 0x15
#define UP_OPCODE_EXECUTE_CODE_END 0x17
#define UP_OPCODE_WRITE 0x02
#define UP_OPCODE_WRITE_ACK 0x03
#define UP_OPCODE_CALC_MD5 0x1F
#define UP_OPCODE_CALC_MD5_ACK 0x20

/*
    BLOCK
*/
#define BLOCK_SIZE (4096)
/*
    WRITE
*/
#define WRITE_SIZE (512)

/*
    发送指令到目标芯片

    opcode  操作码
    addr    操作地址
    length  长度
    src     数据
    print   是否打印
*/
void write_fr8018(uint8_t opcode, uint32_t addr, uint16_t length, uint8_t *src, uint8_t print)
{

    uint8_t *data = (uint8_t *)malloc(TX_BUF_SIZE + 1);
    assert(data != NULL);

    data[0] = opcode;

    data[1] = (addr & 0x000000FF) >> 0;
    data[2] = (addr & 0x0000FF00) >> 8;
    data[3] = (addr & 0x00FF0000) >> 16;
    data[4] = (addr & 0xFF000000) >> 24;

    data[5] = (length & 0x000000FF) >> 0;
    data[6] = (length & 0x0000FF00) >> 8;

    if (length)
    {
        memcpy(&data[7], src, length);
    }

    uart_write_bytes(UART_NUM_1, (const char *)data, length + 7);

    if (print)
        ESP_LOG_BUFFER_HEXDUMP(TX_TASK_TAG, data, length + 7, ESP_LOG_INFO);
    free(data);
}

/*
    解除保护数据
*/
uint8_t Unprotect[] = {
    0x08, 0xb5, 0x00, 0xf0, 0x2e, 0xf8, 0x00, 0x90,
    0xbd, 0xe8, 0x08, 0x40, 0x01, 0x20, 0x00, 0xf0,
    0x3f, 0xb8, 0x00, 0x00, 0x4c, 0xf2, 0x85, 0x3c,
    0xc0, 0xf2, 0x01, 0x0c, 0x60, 0x47, 0x4a, 0xf2,
    0x75, 0x5c, 0xc0, 0xf2, 0x01, 0x0c, 0x60, 0x47,
    0x01, 0x38, 0xfd, 0xd1, 0x70, 0x47, 0x00, 0x00,
    0x38, 0xb5, 0x04, 0x00, 0x14, 0xd0, 0xff, 0xf7,
    0xed, 0xff, 0x04, 0xeb, 0x84, 0x01, 0x01, 0xeb,
    0xc4, 0x11, 0x00, 0x91, 0x18, 0x28, 0x03, 0xd0,
    0x30, 0x28, 0x03, 0xd0, 0x00, 0x98, 0x03, 0xe0,
    0x48, 0x00, 0x00, 0xe0, 0x88, 0x00, 0x00, 0x90,
    0xbd, 0xe8, 0x38, 0x40, 0xff, 0xf7, 0xe4, 0xbf,
    0x38, 0xbd, 0x08, 0xb5, 0x25, 0x48, 0x6b, 0x46,
    0x03, 0x22, 0x00, 0x21, 0x00, 0x68, 0xff, 0xf7,
    0xd6, 0xff, 0x00, 0x98, 0x22, 0x4a, 0x20, 0xf0,
    0x7f, 0x40, 0x00, 0x21, 0xcd, 0xf8, 0x00, 0x00,
    0x52, 0xf8, 0x21, 0x30, 0x83, 0x42, 0x02, 0xd0,
    0x49, 0x1c, 0x05, 0x29, 0xf8, 0xd3, 0x08, 0xbd,
    0x38, 0xb5, 0x1c, 0x4d, 0x04, 0x46, 0x6b, 0x46,
    0x01, 0x22, 0x00, 0x21, 0x28, 0x68, 0xff, 0xf7,
    0xbe, 0xff, 0x9d, 0xf8, 0x00, 0x00, 0x10, 0xf0,
    0x7c, 0x0f, 0x25, 0xd0, 0x20, 0xf0, 0x7c, 0x00,
    0x4f, 0xf0, 0x00, 0x03, 0x8d, 0xf8, 0x00, 0x00,
    0x1a, 0x46, 0x4f, 0xf0, 0x03, 0x01, 0x0c, 0xb1,
    0x11, 0x48, 0x00, 0xe0, 0x11, 0x48, 0x00, 0x68,
    0xff, 0xf7, 0xa9, 0xff, 0x10, 0x48, 0x6b, 0x46,
    0x01, 0x22, 0x02, 0x21, 0x00, 0x68, 0xff, 0xf7,
    0xa2, 0xff, 0x00, 0x2c, 0x0c, 0xd0, 0x01, 0x20,
    0xff, 0xf7, 0xa6, 0xff, 0x6b, 0x46, 0x01, 0x22,
    0x00, 0x21, 0x28, 0x68, 0xff, 0xf7, 0x97, 0xff,
    0x9d, 0xf8, 0x00, 0x00, 0x80, 0x07, 0xf2, 0xd1,
    0x38, 0xbd, 0x00, 0x00, 0x8c, 0xfc, 0x01, 0x00,
    0x14, 0x41, 0x00, 0x20, 0x90, 0xfc, 0x01, 0x00,
    0x98, 0xfc, 0x01, 0x00, 0xac, 0xfc, 0x01, 0x00,
    0x9c, 0xfc, 0x01, 0x00, 0x85, 0x60, 0x14, 0x00,
    0x85, 0x60, 0x13, 0x00, 0x85, 0x60, 0x12, 0x00,
    0xeb, 0x60, 0x13, 0x00, 0xeb, 0x60, 0x14, 0x00};

#define FR801HOK "FR801HOK"

//uart接收超时时间（ms）
#define UART_TIMEOUT 10
//指令等待超时时间（sec）
#define TIMEOUT_SEC ((1000 * 3) / UART_TIMEOUT)

#define FIRMWART_LOG 0

/*
    uart接收处理任务
*/
static void rx_task(void *arg)
{
    //limit
    uint32_t get_firmwart_limit;
    get_number_limit(&get_firmwart_limit);

    //add
    uint8_t setp = 0;
    uint32_t opaddr;
    uint16_t oplen;
    uint32_t gpio_num;
    uint8_t md5_data_len[4];

    //exp
    esp_log_level_set(RX_TASK_TAG, ESP_LOG_INFO);
    uint8_t *data = (uint8_t *)malloc(RX_BUF_SIZE + 1);
    assert(data != NULL);

    //firmwart
    uint32_t firmwart_len;
    uint16_t firmwart_sum;
    uint8_t firmwart_info[8];
    flash_firmwart_info_read(0, firmwart_info, 8);
    firmwart_len = (firmwart_info[0] << 24 | firmwart_info[1] << 16 | firmwart_info[2] << 8 | firmwart_info[3] << 0);
    firmwart_sum = (firmwart_info[4] << 8 | firmwart_info[5] << 0);

    //erase
    uint32_t erase_num;
    uint32_t now_erase_num = 0;
    erase_num = firmwart_len / BLOCK_SIZE;
    if (firmwart_len % BLOCK_SIZE)
        erase_num++;

    //write
    uint32_t write_num;
    uint32_t now_write_num = 0;
    write_num = firmwart_len / WRITE_SIZE;
    if (firmwart_len % WRITE_SIZE)
        write_num++;

    //timeout
    uint32_t time_out = TIMEOUT_SEC;

    //led
    uint8_t led_typed[3];

    //firmwart
    uint8_t *firmwart_data = (uint8_t *)malloc(RX_BUF_SIZE + 1);
    assert(firmwart_data != NULL);

    //md5
    uint32_t md5_count = 0;
    uint32_t now_md5_len = 0;
    uint32_t md5_len = firmwart_len;
    struct MD5Context md5;
    MD5Init(&md5);
    while (md5_len)
    {
        if (md5_len > WRITE_SIZE)
            now_md5_len = WRITE_SIZE;
        else
            now_md5_len = md5_len;

        flash_firmwart_read(md5_count * WRITE_SIZE, firmwart_data, now_md5_len);
        MD5Update(&md5, firmwart_data, now_md5_len);

        md5_count++;
        md5_len -= now_md5_len;
    }
    uint8_t local_file_md5[16];
    memset(local_file_md5, 0, sizeof(local_file_md5));
    MD5Final(local_file_md5, &md5);

    while (1)
    {
        if (xQueueReceive(gpio_evt_queue, &gpio_num, portMAX_DELAY))
        {
            if (gpio_num == BUTTON_2)
            {
                ESP_LOGI(BURN_TAG, "Button2 is press.");
                LED2_ON

                RESET_TARGET
                while (1)
                {
                    const int rxBytes = uart_read_bytes(UART_NUM_1, data, RX_BUF_SIZE, UART_TIMEOUT / portTICK_RATE_MS);

                    if (rxBytes > 0)
                    {
                        data[rxBytes] = 0;

                        switch (setp)
                        {
                        case 0:
                            if (strstr((char *)data, "freqchip"))
                            {
                                time_out = TIMEOUT_SEC;
                                uart_write_bytes(UART_NUM_1, FR801HOK, strlen(FR801HOK));
                                ESP_LOGI(BURN_TAG, "收到标识 [freqchip]");

                                setp++;
                            }
                            break;
                        case 1:
                            if (strstr((char *)data, "ok"))
                            {
                                time_out = TIMEOUT_SEC;
                                ESP_LOGI(BURN_TAG, "收到标识 [ok]");
                                write_fr8018(UP_OPCODE_GET_TYPE, 0, 0, firmwart_data, 1);

                                setp++;
                            }
                            break;
                        case 2:
                            ESP_LOG_BUFFER_HEXDUMP(RX_TASK_TAG, data, rxBytes, ESP_LOG_INFO);

                            if (data[0] == UP_OPCODE_SEND_TYPE)
                            {
                                time_out = TIMEOUT_SEC;
                                opaddr = (data[1] << 0) | (data[2] << 8) | (data[3] << 16) | (data[4] << 24);
                                if (opaddr == 1)
                                {
                                    ESP_LOGI(BURN_TAG, "存储器类型为：flash");
                                    ESP_LOGI(BURN_TAG, "修改波特率：921600");
                                    write_fr8018(UP_OPCODE_CHANGE_BANDRATE, 11, 0, firmwart_data, 1);
                                    setp++;
                                }
                            }
                            break;
                        case 3:
                            ESP_LOG_BUFFER_HEXDUMP(RX_TASK_TAG, data, rxBytes, ESP_LOG_INFO);

                            if (data[0] == UP_OPCODE_CHANGE_BANDRATE_ACK)
                            {
                                time_out = TIMEOUT_SEC;
                                ESP_LOGI(BURN_TAG, "波特率修改成功");

                                uart_config.baud_rate = 921600;
                                uart_param_config(UART_NUM_1, &uart_config);

                                write_fr8018(UP_OPCODE_WRITE_RAM, 0x20004000, 0x128, Unprotect, 1);
                                setp++;
                            }
                            break;
                        case 4:
                            ESP_LOG_BUFFER_HEXDUMP(RX_TASK_TAG, data, rxBytes, ESP_LOG_INFO);

                            if (data[0] == UP_OPCODE_WRITE_RAMACK)
                            {
                                time_out = TIMEOUT_SEC;
                                ESP_LOGI(BURN_TAG, "写解除保护数据成功");

                                write_fr8018(UP_OPCODE_EXECUTE_CODE, 0x20004001, 0, firmwart_data, 1);
                                setp++;
                            }
                            break;
                        case 5:
                            ESP_LOG_BUFFER_HEXDUMP(RX_TASK_TAG, data, rxBytes, ESP_LOG_INFO);

                            if (data[0] == UP_OPCODE_EXECUTE_CODE_END)
                            {
                                time_out = TIMEOUT_SEC;
                                ESP_LOGI(BURN_TAG, "解除保护成功");

                                write_fr8018(UP_OPCODE_BLOCK_ERASE, now_erase_num * BLOCK_SIZE, 0, firmwart_data, 1);
                                now_erase_num++;
                            }

                            if (data[0] == UP_OPCODE_BLOCK_ERASE_ACK)
                            {
                                time_out = TIMEOUT_SEC;
                                ESP_LOGI(BURN_TAG, "擦除成功：%08X", now_erase_num * BLOCK_SIZE);

                                write_fr8018(UP_OPCODE_BLOCK_ERASE, now_erase_num * BLOCK_SIZE, 0, firmwart_data, 1);
                                now_erase_num++;

                                if (now_erase_num == erase_num)
                                    setp++;
                            }
                            break;
                        case 6:
#if FIRMWART_LOG
                            ESP_LOG_BUFFER_HEXDUMP(RX_TASK_TAG, data, rxBytes, ESP_LOG_INFO);
#endif
                            if (data[0] == UP_OPCODE_BLOCK_ERASE_ACK)
                            {
                                time_out = TIMEOUT_SEC;
                                ESP_LOGI(BURN_TAG, "擦除完毕");

                                flash_firmwart_read(now_write_num * WRITE_SIZE, firmwart_data, WRITE_SIZE);
#if FIRMWART_LOG
                                ESP_LOGI(BURN_TAG, "写入地址：%08X", now_write_num * WRITE_SIZE);
#endif
                                write_fr8018(UP_OPCODE_WRITE, now_write_num * WRITE_SIZE, WRITE_SIZE, firmwart_data, 0);
                                now_write_num++;
                            }

                            if (data[0] == UP_OPCODE_WRITE_ACK)
                            {
                                time_out = TIMEOUT_SEC;
                                vTaskDelay(20 / portTICK_RATE_MS);
                                flash_firmwart_read(now_write_num * WRITE_SIZE, firmwart_data, WRITE_SIZE);
#if FIRMWART_LOG
                                ESP_LOGI(BURN_TAG, "写入地址：%08X", now_write_num * WRITE_SIZE);
#endif
                                write_fr8018(UP_OPCODE_WRITE, now_write_num * WRITE_SIZE, WRITE_SIZE, firmwart_data, 0);
                                now_write_num++;

                                if (now_write_num == write_num)
                                    setp++;
                            }
                            break;
                        case 7:
                            ESP_LOG_BUFFER_HEXDUMP(RX_TASK_TAG, data, rxBytes, ESP_LOG_INFO);

                            if (data[0] == UP_OPCODE_WRITE_ACK)
                            {
                                time_out = TIMEOUT_SEC;
                                ESP_LOGI(BURN_TAG, "写入完毕");

                                md5_data_len[3] = (firmwart_len & 0xFF000000) >> 24;
                                md5_data_len[2] = (firmwart_len & 0x00FF0000) >> 16;
                                md5_data_len[1] = (firmwart_len & 0x0000FF00) >> 8;
                                md5_data_len[0] = (firmwart_len & 0x000000FF) >> 0;
                                write_fr8018(UP_OPCODE_CALC_MD5, 0 | 0x01000000, 4, md5_data_len, 1);
                                setp++;
                            }
                            break;
                        case 8:
                            ESP_LOG_BUFFER_HEXDUMP(RX_TASK_TAG, data, rxBytes, ESP_LOG_INFO);

                            if (data[0] == UP_OPCODE_CALC_MD5_ACK)
                            {
                                ESP_LOGI(BURN_TAG, "收到校验信息：");
                                ESP_LOG_BUFFER_HEXDUMP("MD5", &data[7], 16, ESP_LOG_INFO);

                                for (size_t i = 0; i < 16; i++)
                                {
                                    if (local_file_md5[i] != data[i + 7])
                                    {
                                        ESP_LOGE(BURN_TAG, "烧录校验失败");
                                        RESET_TARGET

                                        led_typed[0] = LED_EVENT_TWINKLE;
                                        led_typed[1] = LED_3;
                                        led_typed[2] = 0;
                                        xQueueSend(led_queue, led_typed, NULL);
                                        vTaskDelay(1000 / portTICK_RATE_MS);
                                        vTaskDelay(1000 / portTICK_RATE_MS);
                                        printf("Restarting now.\n");
                                        fflush(stdout);
                                        esp_restart();
                                    }
                                    else
                                    {
                                        if (i == 15)
                                        {
                                            /*
                                                扣减烧录次数
                                            */
                                           if(get_firmwart_limit != 0xFFFFFFFF && get_firmwart_limit != 0)
                                            {
                                                get_firmwart_limit --;
                                                set_number_limit(get_firmwart_limit);
                                            }

                                            ESP_LOGW(BURN_TAG, "烧录校验成功");
                                            RESET_TARGET

                                            led_typed[0] = LED_EVENT_TWINKLE;
                                            led_typed[1] = LED_2;
                                            led_typed[2] = 0;

                                            xQueueSend(led_queue, led_typed, NULL);
                                            BUZZER_H
                                            vTaskDelay(1000 / portTICK_RATE_MS);
                                            BUZZER_L
                                            printf("Restarting now.\n");
                                            fflush(stdout);
                                            esp_restart();
                                        }
                                    }
                                }
                            }
                            break;
                        default:
                            break;
                        }
                    }

                    time_out--;
                    if (!time_out)
                    {
                        ESP_LOGE(BURN_TAG, "通讯超时");

                        led_typed[0] = LED_EVENT_TWINKLE;
                        led_typed[1] = LED_3;
                        led_typed[2] = 0;
                        xQueueSend(led_queue, led_typed, NULL);
                        vTaskDelay(1000 / portTICK_RATE_MS);
                        vTaskDelay(1000 / portTICK_RATE_MS);
                        printf("Restarting now.\n");
                        fflush(stdout);
                        esp_restart();
                    }
                }
            }
        }
    }
    free(data);
}

void burm()
{
    ESP_LOGI(TAG, "init\n");

    init();
    xTaskCreate(rx_task, "uart_rx_task", 1024 * 2, NULL, configMAX_PRIORITIES, NULL);
}
